﻿function Get-Value
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PsObject]$Object,
    [String]$Value,
    [String]$patternExtractor = "\`$\(([^`$\(\)]+)\)"
    )
    Process
    {
        $returnValue = $Value

        #$patternExtractor = "\`$\(([^`$\(\)]+)\)"
        $matches = [System.Text.RegularExpressions.Regex]::Matches($Value,$patternExtractor)

        if ($matches.Count -gt 0)
        {
            Write-Verbose "Replacement pattern have been found in the input $Value"
            foreach($match in $matches)
            {
                #Remove the pattern
                $pattern = $match.value.replace("`$(","")
                $pattern = $pattern.replace("@(","")
                $pattern = $pattern.Substring(0, $pattern.Length -1)

                #Find the scope
                $i = $pattern.IndexOf(":")
                $scope = "local:"
                if ($i -gt 0)
                {
                    $scope = $pattern.Substring(0, $i+1)
                    Write-Verbose "Scope $scope has been found in the input $pattern"
                }
                

                $pattern = $pattern.Replace("$scope","")

                Write-Verbose "Search identifier is $pattern"

                switch($scope)
                {
                    "Configuration:"
                    {
                        $platformConfiguration = Get-PlatformConfiguration -CurrentObject $Object
                        if (-not $platformConfiguration.configuration)
                        {
                            throw "Get-Value request Configuration lookup but no Configuration has been defined in the platform configuration" 
                        }

                        if (-not $platformConfiguration.configuration.datas)
                        {
                            throw "Get-Value request group lookup but no configuration datas have been defined in the platform configuration" 
                        }

                        $ConfigurationDataCollection = $platformConfiguration.configuration.datas
                        $configurationData = $ConfigurationDataCollection.$pattern
                        
                        Write-Debug "Get-Value : Configuration : configurationData = $configurationData"
                        if ($configurationData -eq $null)
                        {
                            throw New-Object System.Exception "Unable to find the Configuration Key $pattern"                            
                        }

                        $environment = Get-EnvironmentTarget -CurrentObject $Object
                        Write-Debug "Get-Value : environment = $environment"
                        $propertyValue = $configurationData.$environment
                        if ($propertyValue -eq $null)
                        {
                            Write-Warning "Null value for $($environment) on configuration Data $pattern, use default value"
                            $propertyValue = $configurationData.Default
                        }

                    }
                    "Group:"
                    {
                        $platformConfiguration = Get-PlatformConfiguration -CurrentObject $Object
                        if (-not $platformConfiguration.Security)
                        {
                            throw "Get-Value request group lookup but no security has been defined in the platform configuration" 
                        }

                        if (-not $platformConfiguration.Security.Groups)
                        {
                            throw "Get-Value request group lookup but no groups have been defined in the platform configuration" 
                        }
                        $propertyValue = $platformConfiguration.Security.Groups
                        write-debug "Get-Value : Search Group : propertyValue = $propertyValue"

                    }
                    "User:"
                    {
                        $platformConfiguration = Get-PlatformConfiguration -CurrentObject $Object
                        if (-not $platformConfiguration.Security)
                        {
                            throw "Get-Value request user lookup but no security has been defined in the platform configuration" 
                        }

                        if (-not $platformConfiguration.Security.Users)
                        {
                            throw "Get-Value request user lookup but no users have been defined in the platform configuration" 
                        }
                        $propertyValue = $platformConfiguration.Security.Users
                        write-debug "Get-Value : Search Group : propertyValue = $propertyValue"

                    }
                    "DeploymentTarget:"{$propertyValue = Get-DeploymentTarget -CurrentObject $Object}
                    "local:"{$propertyValue = $Object}
                    "Parent:"{$propertyValue = $Object.Parent}
                    "Container:"{$propertyValue = $Object.Container}
                    "Server:"{$propertyValue = Get-Server -CurrentObject $Object}
                    "Dependency:"
                    {
                        #Search the nearest Container
                        $dependencyKey=$pattern.Split(".")[0]
                        $pattern = $pattern.replace("$dependencyKey.","")
                        $propertyValue = Get-Dependency -CurrentObject $Object -Key $dependencyKey

                        if (-not $propertyValue)
                        {
                            throw New-Object System.Exception "Unable to find the dependency $dependencyKey"                            
                        }
                    }
                    default
                    {
                        throw New-Object System.Exception "Replacement scope $scope is not yet implemented"
                    }
                }

                if ($scope -ne "Configuration:")
                {
                    if ($propertyValue -ne $null)
                    {
                        $keys = $pattern.Split(".")
                        foreach($key in $keys)
                        {
                            Write-Verbose "Current property Value is $propertyValue"
                            Write-Verbose "Search property $Key"
                            $propertyValue = $propertyValue.$Key
                        }
                    }
                }

                if ($propertyValue -eq $null)
                {
                    write-warning "Detected Null Value on configuration data '$($match.value)'"
                }

                #property value found could be an other redirection to value
                $CheckMultipleRedirection = [System.Text.RegularExpressions.Regex]::Matches($propertyValue,$patternExtractor)
                if ($CheckMultipleRedirection.Count -gt 0)
                {
                    $propertyValue = Get-value -Object $Object -Value $propertyValue
                }

                $returnValue = $returnValue.Replace($match.value, $propertyValue)
            }
        }

        Write-Output $returnValue
    }
}

function Get-PathForFolder
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Object
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-PathForFolder for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-PathForFolder for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    process
    {
        
        #Write-Host $Object.ElementType -ForegroundColor Green
        $Path = ""

        if (($Object.ElementType -eq 'Folder') -or ($Object.ElementType -eq 'File'))
        {
            $Path = Get-Value -Object $Object -Value $Object.Path
            $UniqueKey = $Object.UniqueKey
            if ($Object.ParentFolder)
            {
                $UniqueKey = $Object.ParentFolder
            }

            $ParentObject = $Object.Parent
            if ($ParentObject)
            {
                if ($ParentObject.ElementType -eq 'Folder')
                {
                    $ParentPath = Get-PathForFolder -Object $ParentObject
                    $Path = [System.IO.Path]::Combine($ParentPath, $Path)
                }
            }
        }

        Write-Output $Path

    }
}


Function Get-FullPath
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Object
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-FullPath for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-FullPath for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    process
    {
        
        #Write-Host $Object.ElementType -ForegroundColor Magenta

        #Unable to determine a Full Path for server
        if ($Object.ElementType -eq 'Server')
        {
            return
        }

        $Path = Get-PathForFolder -Object $Object
        $rootObject = Get-RootObjectInsideContainer -Object $Object

        if (($rootObject.ElementType -eq 'Folder') -or ($rootObject.ElementType -eq 'File'))
        {
            Write-Verbose "Check if this Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder '$($rootObject.ParentFolder)'"

            #Le root object n'est pas relatif. On peut donc donner la valeur
            if (!$rootObject.Relative){return $Path}

            if ($rootObject.ParentFolder)
            {
                $UniqueKey = $rootObject.ParentFolder
                Write-Verbose "Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder $($rootObject.ParentFolder)"
                #Write-Host "Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder $($rootObject.ParentFolder)" -ForegroundColor Green
                $folder = Find-ObjectInCollection -Container $rootObject.Container -PropertyName Folders -Uniquekey $rootObject.ParentFolder
                if ($folder)
                {
                    Write-Verbose "Find $($folder.ElementType) identified by $($folder.UniqueKey) on the same container"
                    #Write-Host "Find $($folder.ElementType) identified by $($folder.UniqueKey) on the same container" -ForegroundColor Yellow
                    $ParentPath = Get-FullPath -Object $folder
                    return [System.IO.Path]::Combine($ParentPath, $Path)
                }
            }
            else
            {
                $UniqueKey = $rootObject.UniqueKey
            }

            #Récupération du container du root Object
            $Object = $rootObject.Container
            Write-Verbose "Retrieve the root object container $($Object.ElementType) identified by $($Object.UniqueKey)"

        }

        #On change la unique 
        if ($Object.ParentFolder)
        {
            $UniqueKey = $Object.ParentFolder
        }
        
        if ($Object.Parent)
        {
            $CurrentParent = $Object.parent
            while($CurrentParent -ne $null)
            {

                #Write-Host "Check full path on object $($CurrentParent.ElementType) identified by $($CurrentParent.UniqueKey) with uniqueKey '$UniqueKey'" -ForegroundColor Green
                if ($UniqueKey)
                {
                    $folder = Find-ObjectInCollection -Container $CurrentParent -PropertyName Folders -Uniquekey $UniqueKey
                    if ($folder)
                    {
                        $ParentPath = Get-FullPath -Object $folder
                        $Path = [System.IO.Path]::Combine($ParentPath, $Path)
                    }
                }

                #Before changing of current object, we switch the uniqueKey if parent folder attribute exist
                if ($CurrentParent.ParentFolder)
                {
                    $UniqueKey = $CurrentParent.ParentFolder                    
                }

                $CurrentParent = $CurrentParent.Parent 
                
            }
        }

        Write-Output $Path

    }
}


Function Get-SharePath
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Object
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-SharePath for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-SharePath for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    process
    {
        
        #Write-Host $Object.ElementType -ForegroundColor Magenta

        #Unable to determine a Full Path for server
        if ($Object.ElementType -eq 'Server')
        {
            return
        }

        $Path = Get-PathForFolder -Object $Object
        $rootObject = Get-RootObjectInsideContainer -Object $Object

        if (($rootObject.ElementType -eq 'Folder') -or ($rootObject.ElementType -eq 'File'))
        {
            Write-Verbose "Check if this Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder '$($rootObject.ParentFolder)'"

            #Le root object n'est pas relatif. On peut donc donner la valeur
            if ($rootObject.Share)
            {
                $server = Get-Server -CurrentObject $Object
                $shareServer = "\\{0}" -f $server.HostName
                $shareServerFQDN = "\\{0}" -f $server.HostNameFQDN

                #TODO YGO : This algorithm should be reuse
                if ($server.HostName -and $server.HostNameFQDN)
                {
                    if ($server.PreferredHostNameConnection -eq 'FQDN')
                    {
                        return [System.IO.Path]::Combine($shareServerFQDN, $rootObject.Share)
                    }
                    else
                    {
                        return [System.IO.Path]::Combine($shareServer, $rootObject.Share)
                    }
                }
                else
                {
                    if ($shareServerFQDN)
                    {
                        return [System.IO.Path]::Combine($shareServerFQDN, $rootObject.Share)
                    }
                    else
                    {
                        return [System.IO.Path]::Combine($shareServer, $rootObject.Share)
                    }
                }

            }

            if ($rootObject.ParentFolder)
            {
                $UniqueKey = $rootObject.ParentFolder
                Write-Verbose "Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder $($rootObject.ParentFolder)"
                #Write-Host "Root object $($rootObject.ElementType) identified by $($rootObject.UniqueKey) has a parent folder $($rootObject.ParentFolder)" -ForegroundColor Green
                $folder = Find-ObjectInCollection -Container $rootObject.Container -PropertyName Folders -Uniquekey $rootObject.ParentFolder
                if ($folder)
                {
                    Write-Verbose "Find $($folder.ElementType) identified by $($folder.UniqueKey) on the same container"
                    #Write-Host "Find $($folder.ElementType) identified by $($folder.UniqueKey) on the same container" -ForegroundColor Yellow
                    $ParentPath = Get-SharePath -Object $folder
                    return [System.IO.Path]::Combine($ParentPath, $Path)
                }
            }
            else
            {
                $UniqueKey = $rootObject.UniqueKey
            }

            #Récupération du container du root Object
            $Object = $rootObject.Container
            Write-Verbose "Retrieve the root object container $($Object.ElementType) identified by $($Object.UniqueKey)"

        }

        #On change la unique 
        if ($Object.ParentFolder)
        {
            $UniqueKey = $Object.ParentFolder
        }
        
        if ($Object.Parent)
        {
            $CurrentParent = $Object.parent
            while($CurrentParent -ne $null)
            {

                #Write-Host "Check full path on object $($CurrentParent.ElementType) identified by $($CurrentParent.UniqueKey) with uniqueKey '$UniqueKey'" -ForegroundColor Green
                if ($UniqueKey)
                {
                    $folder = Find-ObjectInCollection -Container $CurrentParent -PropertyName Folders -Uniquekey $UniqueKey
                    if ($folder)
                    {
                        $ParentPath = Get-SharePath -Object $folder
                        $Path = [System.IO.Path]::Combine($ParentPath, $Path)
                    }
                }

                #Before changing of current object, we switch the uniqueKey if parent folder attribute exist
                if ($CurrentParent.ParentFolder)
                {
                    $UniqueKey = $CurrentParent.ParentFolder                    
                }

                $CurrentParent = $CurrentParent.Parent 
                
            }
        }

        Write-Output $Path

    }
}



function Get-RootObjectInsideContainer
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Object
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-RootObjectInsideContainer for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-RootObjectInsideContainer for object $($Object.ElementType) identified by  $($Object.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    process
    {


        if ((-not $Object.Parent) -and (-not $Object.Container))
        {
            #the object is the root object.
            #TODO YGO : return $null or return itself ??
            return $null
        }
        else
        {
            if (($Object.Parent) -and (-not $Object.Container))
            {
                #Search root Object from ist parent
                return Get-RootObjectInsideContainer -Object $Object.Parent                
            }

            if (($Object.Container) -and (-not $Object.Parent))
            {
                #Object this object is the root object 
                Write-Verbose "Found object type $($Object.ElementType) identified by  $($Object.UniqueKey)"
                return $Object
            }

            $CurrentObject = $Object
            while($currentObject.Parent.ElementType -ne $CurrentObject.Container.ElementType)
            {
                $CurrentObject = $CurrentObject.Parent
                if ($CurrentObject -eq $null) {return}
            }
            Write-Verbose "Found object type $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
            Write-Output $CurrentObject

        }
    }
}

function Get-RootFolder
{
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Server,

    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [String]$UniqueKey
    )
    begin
    {
        Write-Verbose "Start Get-RootFolder for $UniqueKey from $($Server.UniqueKey)"
    }
    Process
    {
        $folder = $Server.Folders | ? {($_.Level -eq 'Server') -and ($_.UniqueKey -eq $UniqueKey)}

        if ($folder)
        {
            Write-Output $folder
        }
        else
        {
            throw New-Object System.Exception "Missing root folder $UniqueKey on Server $($Server.Name)"
        }
    }
}


function Get-Server
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -Message "Start" -AdditionalData $PSBoundParameters)
    }
    End
    {
        $watch.Stop()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -UniqueId $eventId -Message "End" -AdditionalData @{Duration=$watch.Elapsed})
    }
    Process
    {

        if ($CurrentObject.ElementType -eq 'Server')
        {
            return $CurrentObject
        }

        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            throw New-Object System.Exception "By Design, Unable to find the related server from element $($CurrentObject.ElementType) with UniqueKey $($CurrentObject.UniqueKey)"
        }

        if (-not $CurrentObject.Container)
        {
            throw New-Object System.Exception "Container is null from element $($CurrentObject.ElementType) with UniqueKey $($CurrentObject.UniqueKey)"
        }
        else
        {
            Get-Server -CurrentObject $CurrentObject.Container             
        } 

    }
}


function Get-PlatformConfiguration
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -Message "Start" -AdditionalData $PSBoundParameters)
    }
    End
    {
        $watch.Stop()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -UniqueId $eventId -Message "End" -AdditionalData @{Duration=$watch.Elapsed})
    }
    Process
    {

        if ($CurrentObject.ElementType -eq 'deploymentTarget')
        {
            $CurrentObject.PlatformConfiguration
        }
        else
        {
            $server = Get-Server -CurrentObject $CurrentObject
            if (-not $server.PlatformConfiguration)
            {
                throw "PlatformConfiguration is null from element $($server.ElementType) with UniqueKey $($server.UniqueKey)"
            }

            #return the platform configuration
            $server.PlatformConfiguration
        }

    }
}



function Get-DeploymentTarget
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-DeploymentTarget for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-DeploymentTarget for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            return $CurrentObject
        }

        if (-not $CurrentObject.Container)
        {
            throw New-Object System.Exception "Container is null from element $($CurrentObject.ElementType) with UniqueKey $($CurrentObject.UniqueKey)"
        }
        else
        {
            Get-DeploymentTarget -CurrentObject $CurrentObject.Container             
        } 

    }
}


function Get-EnvironmentTarget
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-EnvironmentTarget for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-EnvironmentTarget for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {
        #Check ig the current object have a Environment Value
        $value = $CurrentObject.Environment
        if ($value) {return $Value;}

        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            throw New-Object System.Exception "No object have been found with a targeted environment until the deployment Target $($CurrentObject.ElementType) with UniqueKey $($CurrentObject.UniqueKey)"
        }

        if (-not $CurrentObject.Parent)
        {
            throw New-Object System.Exception "Don't find any environment and the configuration pipeline is terminated on current element $($CurrentObject.ElementType) with UniqueKey $($CurrentObject.UniqueKey)"
        }
        else
        {
            Get-EnvironmentTarget -CurrentObject $CurrentObject.Parent             
        } 

    }
}




function Get-DependenciesContainer
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-DependenciesContainer for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-DependenciesContainer for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        if ($CurrentObject.Dependencies)
        {
            return $CurrentObject
        }
        else
        {
            if ($CurrentObject.Parent)
            {
                return Get-DependenciesContainer -CurrentObject $CurrentObject.Parent
            }

            if ($CurrentObject.Container)
            {
                return Get-DependenciesContainer -CurrentObject $CurrentObject.Container
            }


            if ($CurrentObject.ElementTYpe -ne 'Server')
            { 
                throw New-Object System.Exception "Design Eror . It should always have a Parent or a container"
            }

        }

    }
}


function Get-Dependency
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [PSObject]$CurrentObject,
    [String]$Key
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-Dependency for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Get-Dependency for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        $dependencyContainer = Get-DependenciesContainer -CurrentObject $CurrentObject
        $dependency = $dependencyContainer.Dependencies.$Key

        switch($dependency.type)
        {
            "product"
            {
                $server = Get-Server -CurrentObject $dependency
                $product = $server.Products | ? {$_.UniqueKey -eq $dependency.productUniqueKey} | Select-Object -First 1
                Write-Output $product
            }
            default
            {
                throw New-Object System.NotImplementedException "$($dependency.type) is not ready"
            }
        }
    }
}



function Calculate-ConfigurationLocationPath
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Calculate-ConfigurationLocationPath for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Calculate-ConfigurationLocationPath for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {
        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            return $null
        }

        switch($CurrentObject.ElementType)
        {
            #"folder" {$Path = $CurrentObject.Path}
            "Server" {$Path = $CurrentObject.hostName}
            default {$Path = $CurrentObject.UniqueKey}
        }

        
        if ($CurrentObject.Container)
        {
            $ContainerPath = Calculate-ConfigurationLocationPath -CurrentObject $CurrentObject.Container
            $Path = [System.IO.Path]::Combine($ContainerPath,$Path)
        }

        Write-Output $Path
    }
}



function Resolve-FullQualifiedUniqueKey
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Resolve-FullQualifiedUniqueKey for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Resolve-FullQualifiedUniqueKey for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {
        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            return "$($CurrentObject.environment).$(Get-value -Object $CurrentObject -Value $CurrentObject.applicationName).$($CurrentObject.version)"
        }

        switch($CurrentObject.ElementType)
        {
            #"folder" {$Path = $CurrentObject.Path}
            "Server" {$uniqueKey = $CurrentObject.hostName}
            default {$uniqueKey = $CurrentObject.UniqueKey}
        }

        
        if ($CurrentObject.Parent)
        {
            $ContainerUniqueKey = Resolve-FullQualifiedUniqueKey -CurrentObject $CurrentObject.Parent
            if ($ContainerUniqueKey) 
            {
               if ($uniqueKey) {$uniqueKey = "$ContainerUniqueKey.$uniqueKey"}
               else {$uniqueKey = "$ContainerUniqueKey"}
            }
        }

        Write-Output $uniqueKey
    }
}




function Calculate-ConfigurationFullQualifiedUniqueKey
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Calculate-ConfigurationFullQualifiedUniqueKey for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
    }
    End
    {
        write-verbose "End Calculate-ConfigurationFullQualifiedUniqueKey for object $($CurrentObject.ElementType) identified by  $($CurrentObject.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {
        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            return $null
        }

        switch($CurrentObject.ElementType)
        {
            #"folder" {$Path = $CurrentObject.Path}
            "Server" {$uniqueKey = $CurrentObject.hostName}
            default {$uniqueKey = $CurrentObject.UniqueKey}
        }

        
        if ($CurrentObject.Parent)
        {
            $ContainerUniqueKey = Calculate-ConfigurationFullQualifiedUniqueKey -CurrentObject $CurrentObject.Parent
            if ($ContainerUniqueKey) 
            {
               if ($uniqueKey) {$uniqueKey = "$ContainerUniqueKey.$uniqueKey"}
               else {$uniqueKey = "$ContainerUniqueKey"}
            }
        }

        Write-Output $uniqueKey
    }
}

function Calculate-BaseConfigurationRelativePath
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [PSObject]$CurrentObject
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -Message "Start" -AdditionalData $PSBoundParameters)
    }
    End
    {
        $watch.Stop()
        $eventId = Publish-ToMiddlewareLogs -Log (Get-LogMessage -Command $PSCmdlet.MyInvocation.MyCommand -UniqueId $eventId -Message "End" -AdditionalData @{Duration=$watch.Elapsed})
    }
    Process
    {
        if ($CurrentObject.ElementType -eq 'DeploymentTarget')
        {
            return $null
        }

        switch($CurrentObject.ElementType)
        {
            #"folder" {$Path = $CurrentObject.Path}
            "Server" {$Path = "Server"}
            default {$Path = $CurrentObject.UniqueKey}
        }

        
        if ($CurrentObject.Container)
        {
            $ContainerPath = Calculate-BaseConfigurationRelativePath -CurrentObject $CurrentObject.Container
            $Path = [System.IO.Path]::Combine($ContainerPath,$Path)
        }

        Write-Output $Path

    }
}


function Get-EnsureStatus
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$CurrentObject
    )
    Process
    {

        if ($CurrentObject.Ensure)
        {
            Write-Verbose "Current Object $($CurrentObject.ElementType) identified by $($CurrentObject.UniqueKey) has Ensure Property"
            $Value = $CurrentObject.Ensure
            if ([String]::IsNullOrWhiteSpace($Value))
            {
                Write-warning "Current Object $($CurrentObject.ElementType) identified by $($CurrentObject.UniqueKey) has an empty Ensure Property, return Present"
                return "Present"
            }
            else
            {
                return $Value
            }
        }

        if ($CurrentObject.Parent)
        {
            return Get-EnsureStatus -CurrentObject $CurrentObject.Parent 
        }
        else
        {
            if ($CurrentObject.Container)
            {
                return Get-EnsureStatus -CurrentObject $CurrentObject.Container
            }
            else
            {
                Write-warning "Current Object $($CurrentObject.ElementType) identified by $($CurrentObject.UniqueKey) has no Ensure Property, return Present"
                #No field in the object tree
                return "Present"
            }
        }

    }
}


Function Get-RegistryValue
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$Registry
    )
    Process
    {
        $Value = Get-Value -Object $Registry -Value $Registry.ValueData

        switch($Registry.Parent.ElementType.tolower())
        {
            "folder" {$Value += Get-Value -Object $Registry -Value (Get-FullPath -Object $Registry.Parent)}
            "file" {$Value += Get-Value -Object $Registry -Value (Get-FullPath -Object $Registry.Parent)}
            "javaoption" {$Value += Get-JavaOption -JavaOption $Registry.Parent}
        }

        Write-Output $Value

    }

}

Function Get-JavaOption
{
    [cmdletbinding(DefaultParameterSetName="none")]
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$JavaOption
    )
    Process
    {
        $Value = Get-Value -Object $JavaOption -Value $JavaOption.Value

        switch($JavaOption.Parent.ElementType.tolower())
        {
            "folder" {$Value += Get-Value -Object $JavaOption -Value (Get-FullPath -Object $JavaOption.Parent)}
            "file" {$Value += Get-Value -Object $JavaOption -Value (Get-FullPath -Object $JavaOption.Parent)}
        }

        Write-Output "-$($JavaOption.Name)=$Value"

    }

}

Function Merge-Registry
{
    param(
    [PSObject]$Registries
    )
    Process
    {
        $RegistryGroups = $Registries | Group-Object UniqueKey
        foreach($RegistryGroup in $RegistryGroups)
        {
            $UniqueKey = $null
            $key = $null
            $ValueName = $null
            $ValueData = $null
            $ValueType = $null
            $Ensure = $null
            $MergePattern = $null

            $RegistryGroupsValue = $RegistryGroup.Group | Sort-Object ValueData

            foreach($Registry in $RegistryGroupsValue)
            {
                $UniqueKey = $Registry.UniqueKey
                $key = $Registry.key
                $ValueName = $Registry.ValueName
                $ValueType = $Registry.ValueType
                $Ensure = $Registry.Ensure
                $MergePattern = $Registry.MergePattern
                
                if ($ValueData)
                {
                    $ValueData+= "$($Registry.MergePattern)$($Registry.ValueData)"
                }
                else
                {
                    $ValueData = $Registry.ValueData
                }

            }


            #MultiString concerns RED_MULTI_SZ which is an array of String so we have to split its value and convert the type in String
            if ($ValueType -eq "Multistring")
            {
                $ValueData = $ValueData.Split($MergePattern,[StringSplitOptions]::RemoveEmptyEntries)
            }



            #return the merged object 
            New-Object PSObject -Property @{
                                            UniqueKey=$UniqueKey
                                            Key=$key
                                            ValueName=$ValueName
                                            ValueType=$ValueType
                                            ValueData=$ValueData
                                            Ensure=$Ensure
                                            }
        }
    }
}


function Get-EnvironmentVariable
{
    param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [PSObject]$EnvironmentVariable
    )
    Process
    {

        $Value = Get-Value -Object $Registry -Value $Registry.ValueData

        switch($EnvironmentVariable.Parent.ElementType.tolower())
        {
            "folder" {$Value += Get-Value -Object $EnvironmentVariable -Value (Get-FullPath -Object $EnvironmentVariable.Parent)}
            "file" {$Value += Get-Value -Object $EnvironmentVariable -Value (Get-FullPath -Object $EnvironmentVariable.Parent)}
        }

        Write-Output $Value
    }
}

Function Merge-EnvironmentVariables
{
    param(
    [PSObject]$EnvironmentVariables
    )
    Process
    {
        $EnvironmentVariablesGroups = $EnvironmentVariables | Group-Object Name
        foreach($EnvironmentVariablesGroup in $EnvironmentVariablesGroups)
        {
            $UniqueKey = $null
            $Name = $null
            $Path = $null
            $Value = $null
            $Ensure = $null


            foreach($EnvironmentVariable in $EnvironmentVariablesGroup.Group)
            {
                $UniqueKey = $EnvironmentVariable.Name
                $Name = $EnvironmentVariable.Name
                $Path = $EnvironmentVariable.Path
                $Ensure = $EnvironmentVariable.Ensure
                
                if ($Value)
                {
                    $Value+= "$($EnvironmentVariable.MergePattern)$($EnvironmentVariable.Value)"
                }
                else
                {
                    $Value = $EnvironmentVariable.Value
                }

            }

            #return the merged object 
            New-Object PSObject -Property @{
                                            UniqueKey=$UniqueKey
                                            Name=$Name
                                            Path=$Path
                                            Ensure=$Ensure
                                            Value=$Value
                                            }
        }
    }
}


#TODO : La règle un seul middleware par type est fausse. Vrai pour du IIS mais pas pour les autres

function Get-HostedExecutionContainer
{
    [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$Server,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$MiddlewareType,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$UniqueKey

    )
    Process
    {

        #Find the Middleware according its type. We must have only one middleware per server.
        #Multi-instance is managed by multiple execution Containers
        $middleware = $Server.Middlewares.Values | ? {$_.Type -eq $MiddlewareType}

        if (-not $middleware)
        {
            throw New-Object System.Exception "Middleware type $MiddlewareType is not defined on server $($Server.hostName)"                        
        }


        $executionContainer = $middleware.ExecutionContainers.$UniqueKey
        if (-not $executionContainer)
        {
            throw New-Object System.Exception "executionContainer $UniqueKey is not defined for the Middleware type $MiddlewareType on server $($Server.UniqueKey)"                        
        }
        
        write-verbose "Found ExecutionContainer $UniqueKey for the Middleware type $MiddlewareType on server $($Server.UniqueKey)" 
        Write-Output $executionContainer

    }
}

function Get-MiddlewareRuntime
{
    [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$Server,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$MiddlewareType,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$UniqueKey

    )
    Process
    {

        #Find the Middleware according its type. We must have only one middleware per server.
        #Multi-instance is managed by multiple execution Containers
        $middleware = $Server.Middlewares.Values | ? {$_.Type -eq $MiddlewareType}

        if (-not $middleware)
        {
            throw New-Object System.Exception "Middleware type $MiddlewareType is not defined on server $($Server.hostName)"                        
        }


        $runtime = $middleware.Runtimes.$UniqueKey
        if (-not $runtime)
        {
            throw New-Object System.Exception "runtime $UniqueKey is not defined for the Middleware type $MiddlewareType on server $($Server.UniqueKey)"                        
        }
        
        write-verbose "Found runtime $UniqueKey for the Middleware type $MiddlewareType on server $($Server.UniqueKey)" 
        Write-Output $runtime

    }
}



function Find-ObjectInCollection
{
    
    [cmdletbinding(DefaultParameterSetName="none")]
    Param( 
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [PSObject]$Container,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$PropertyName,

        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$Uniquekey
    )
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Find-ObjectInCollection to collection $PropertyName for object identified by  $UniqueKey on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
    }
    End
    {
        write-verbose "End Find-ObjectInCollection to collection $PropertyName for object identified by  $UniqueKey on Container $($Container.ElementType) identified by  $($Container.UniqueKey)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }    
    Process
    {

        #If the container does't have this collection, exit the function
        if ($Container.$($PropertyName) -eq $null) {return}

        #retrieve the type of collection and add the element
        $collectionType = $Container.$($PropertyName).GetType()
        switch($collectionType.Name.ToLower())
        {
            "hashtable"
            {
                #return the uniqueKey from the collection
                $Container.$($PropertyName).$($Uniquekey)
            }
            "object[]" 
            {
                $Container.$($PropertyName) | ? {($_.UniqueKey -eq $Uniquekey) -and ($_.Level -eq $Container.ElementType)}
            }
            default {throw New-Object System.Exception "Collection of $collectionType is not managed in Add-ObjectToCOllection cmdlet"}
        }
    }
}
